#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <experimental/filesystem> //使用C++17文件系统库，编译选项要加-lstdc++fs
#include <jsoncpp/json/json.h>     //使用jsoncpp库，编译选项要加上-ljsoncpp
#include "bundle.h"                //bundle库里面使用了线程，所以编译时要加上-lpthread

using std::cout;
using std::endl;

namespace cloud
{
    namespace fs = std::experimental::filesystem;

    // 文件操作工具类
    class FileUtil
    {
    public:
        // 构造函数
        FileUtil(const std::string &filename)
            : _filename(filename)
        {
        }

        // 删除文件
        // 使用C库函数int remove(const char *pathname);删除指定文件
        bool Remove()
        {
            if (Exists() == false)
                return true;
            remove(_filename.c_str());
            return true;
        }

        // 获取文件大小
        int64_t FileSize()
        {
            // 通过系统调用int stat(const char *pathname, struct stat *statbuf);获取
            // 成功返回0，失败返回-1
            // 传入文件路径pathname，改接口会将文件相关属性放入结构体变量statbuf中（输入输出型参数）
            // statbuf成员中就有st_size字段，即为文件大小，单位是字节

            struct stat st;
            if (stat(_filename.c_str(), &st) < 0) // 获取失败
            {
                cout << "get file size failed" << endl;
                return -1;
            }
            return st.st_size; // 获取成功则返回文件大小
        }

        // 获取文件最后一次修改时间 M->modify
        time_t LastMTime()
        {
            // 同上，只不过最后返回的字段是st_mtime
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0) // 获取失败
            {
                cout << "get file last modification time failed" << endl;
                return -1;
            }
            return st.st_mtime; // 获取成功则返回文件最后一次修改时间
        }

        // 获取文件最后一次访问时间 A->access
        time_t LastATime()
        {
            // 同上，只不过最后返回的字段是st_atime
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                cout << "get file last access time failed" << endl;
                return -1;
            }
            return st.st_atime;
        }

        // 获取文件名
        std::string FileName()
        {
            //_filename可能是带路径的，如：./d1/test.txt
            size_t pos = _filename.find_last_of('/'); // 获取最后一个路径分隔符的下标，find_last_of从字符串末尾查找
            if (pos == std::string::npos)             // 如果没找到/，则说明_filename本身就是一个纯文件名
                return _filename;
            return _filename.substr(pos + 1); // 如果找到，则返回/后面的字符串（即文件名）
        }

        // 从指定位置pos开始获取指定长度len（字节数）的文件数据放到body中
        bool GetPosLen(std::string &body, size_t pos, size_t len)
        {
            size_t fsize = FileSize(); // 调用FileSize接口获取文件大小
            if (pos + len > fsize)     // 获取内容不合法
            {
                cout << "get content is illegal!" << endl;
                return false;
            }
            std::ifstream ifs;                     // 输入文件流对象
            ifs.open(_filename, std::ios::binary); // 以二进制方式打开文件
            if (ifs.is_open() == false)            // 调用is_open接口可检查文件是否被成功打开
            {
                std::cout << "GetPosLen open file failed!" << endl;
                return false;
            }

            // istream& seekg (streamoff off, ios_base::seekdir way);
            // off：偏移量（正数向后，负数向前）
            // way有三种取值
            // ios_base::beg	流的开始
            // ios_base::cur	流的当前位置
            // ios_base::end	流的结尾
            ifs.seekg(pos, std::ios::beg); // 使用seekg重新定位流位置指示器到pos位置
            body.resize(len);              // 调用string的resize接口提前给body开好len个空间（capacity和size都将变为len）

            ifs.read(&body[0], len); // 调用read将长度为len的数据写入body里
            if (ifs.good() == false) // good接口可以检查流的状态是否良好，在这里可用来判断read是否正常完成
            {
                std::cout << "get file content failed" << endl;
                ifs.close();
                return false;
            }

            ifs.close();
            return true;
        }

        // 获取文件的所有数据（直接复用GetPosLen)放入body中
        bool GetContent(std::string &body)
        {
            size_t fsize = FileSize();
            return GetPosLen(body, 0, fsize);
        }

        // 将body的内容写入文件
        bool SetContent(const std::string &body)
        {
            std::ofstream ofs; // 输出文件流对象
            ofs.open(_filename, std::ios::binary);
            if (ofs.is_open() == false)
            {
                std::cout << "SetContent open file failed!" << endl;
                return false;
            }

            ofs.write(&body[0], body.size()); // 将字符串写入文件
            if (ofs.good() == false)          // good接口可以检查流的状态是否良好，在这里可用来判断write是否正常完成
            {
                std::cout << "write file content failed!" << endl;
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        // 压缩文件放至指定压缩包packname中
        bool Compress(const std::string &packname)
        {
            // 1.获取源文件数据到字符串中
            std::string body;
            if (GetContent(body) == false)
            {
                cout << "Compress get file content failed!" << endl;
                return false;
            }

            // 2.对数据进行压缩
            // 借助bundle库的pack接口对数据进行lzip格式的压缩
            // 第一个参数指定压缩格式，第二个参数传要压缩的数据
            std::string packed = bundle::pack(bundle::LZIP, body);

            // 3.将压缩后的数据放入压缩文件中
            FileUtil fu(packname); // 实例化一个FileUtil 对象
            // 调用SetConent接口将压缩后的数据写入压缩包文件packname
            if (fu.SetContent(packed) == false)
            {
                cout << "Compress write packed data failed!" << endl;
                return false;
            }
            return true;
        }

        // 将压缩文件解压到指定文件filename中
        bool UnCompress(const std::string &filename)
        {
            // 1.将压缩包中的数据读取出来
            std::string body;
            if (GetContent(body) == false)
            {
                cout << "UnCompress get file content failed!" << endl;
                return false;
            }

            // 2.对数据进行解压缩
            // 使用bundle库中的unpack接口对数据进行解压缩
            // 只有一个参数，输入要解压缩的数据即可
            std::string unpacked = bundle::unpack(body);

            // 3.将解压缩后的数据写入新文件
            FileUtil fu(filename); // 实例化一个FileUtil 对象
            // 调用SetConent接口将压缩后的数据写入压缩包文件packname
            if (fu.SetContent(unpacked) == false)
            {
                cout << "UnCompress write packed data failed!" << endl;
                return false;
            }
            return true;
        }

        // 检测文件/目录是否存在与文件系统中
        bool Exists()
        {
            // 使用C++17文件系统库中的exists函数
            // bool exists( const path& p )p可以是文件名或路径
            return fs::exists(_filename);
        }

        // 创建目录
        bool CreateDirectory()
        {
            if (Exists())
                return true;
            // 使用C++17文件系统库中的create_directories函数创建目录
            // bool create_directories( const path& p ); 传入路径即可
            return fs::create_directories(_filename);
        }

        // 遍历目录获取所有的文件的相对路径信息
        bool ScanDirectory(std::vector<std::string> &v)
        {
            for (auto &p : fs::directory_iterator(_filename)) // 遍历该目录的目录迭代器
            {
                if (fs::is_directory(p)) // 如果是目录，则跳过，因为我们只获取文件信息
                    continue;

                v.push_back(fs::path(p).relative_path().string());
                // 这里的p并不是string类型，先用p构造一个path类对象，再调用relative_path获取相对路径
                // 但relative_path返回值也是path对象，所以再调用它的string接口转换成string类
            }
            return true;
        }

    private:
        std::string _filename; // 文件/目录名（可带路径）
    };

    // json实用工具类实现
    class JsonUtil
    {
        // 两个函数都实现成了静态成员函数，静态成员所有类对象所共享，不属于某个具体的对象，存放在静态区
        // 静态成员函数无需实例化对象来调用，也没有this指针，通过访问类域即可调用

    public:
        // json序列化：使用Json::StreamWriter类的write接口可以完成序列化
        // Serialize接收一个Json::Value对象，转换成json格式的字符串存入str
        static bool Serialize(const Json::Value &root, std::string &str)
        {
            // StreamWriter是一个抽象类，不能直接实例化对象
            // 需要调用StreamWriterBuilder类的newStreamWriter()接口来构建StreamWriter对象
            Json::StreamWriterBuilder swb;

            // newStreamWriter接口返回对象的指针，这里使用智能指针来进行资源管理
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

            // 调用write接口即可完成序列化
            // 第一个参数接收要序列化的Json::Value对象，第二个参数接收一个ostream的输出流对象来接收结果
            std::stringstream ss;          // 定义一个字符串流对象
            if (sw->write(root, &ss) != 0) // write成功返回0
            {
                std::cout << "Serialize write failed!\n";
                return false;
            }
            // 将流对象中的字符串赋给str
            str = ss.str();
            return true;
        }

        // 反序列化：将json格式的字符串解析回json::value对象
        // 使用CharReader类的parse接口可以完成反序列化
        static bool UnSerialize(const std::string &str, Json::Value &root)
        {
            // 同样的，CharReader也是一个抽象类，需要使用CharReaderBuilder类的newCharReader()接口创建
            Json::CharReaderBuilder crb;
            // 同样使用智能指针来进行对象资源的管理
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            // bool parse(char const* beginDoc, char const* endDoc,Value* root, std::string* errs)
            // 第一个参数为字符串的起始地址，第二个为结束地址，第三个参数传json::value对象
            // 第四个参数可用来获取错误信息
            std::string err;
            bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
            if (ret == false)
            {
                std::cout << "parse error: " << err << std::endl;
                return false;
            }
            return true;
        }
    };
}